﻿using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Cef.Utility
{
    public static class HuaXuReader
    {
        private static object comboBox_m1Card_block_num;
        #region API声明

        //二代证相关API---------------------------------------------------------------------------------------
        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_StartFindIDCard(int iPort, byte[] pucManaInfo, int iIfOpen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_SelectIDCard(int iPort, byte[] pucManaMsg, int iIfOpen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_ReadBaseMsg(int iPort, byte[] pucCHMsg, ref uint puiCHMsgLen, byte[] pucPHMsg, ref uint puiPHMsgLen, int iIfOpen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_ReadBaseFPMsg(int iPort, byte[] pucCHMsg, ref uint puiCHMsgLen, byte[] pucPHMsg, ref uint puiPHMsgLen, byte[] pucFPMsg, ref uint puiFPMsgLen, int iIfOpen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_GetSAMIDToStr(int iPort, StringBuilder pcSAMID, int iIfOpen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int SDT_ReadNewAppMsg(int iPort, byte[] pucAppMsg, ref uint puiAppMsgLen, int iIfOpen);

        //照片解码API---------------------------------------------------------------------------------------

        [DllImport("DLL_File.dll", CallingConvention = CallingConvention.Cdecl)]//注意这里的调用方式为Cdecl
        static extern int unpack(byte[] pi_szSrcWltData, byte[] szDstPicData, int iIsSaveToBmp);

        //设备相关API---------------------------------------------------------------------------------------
        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int CarderOpen(int iCom, int iBaud);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int CarderClose();

        //IC卡相关API---------------------------------------------------------------------------------------
        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int CardActive(byte ucCardNo, byte[] ATR, ref int nATRLen);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int CardAPDU(byte ucCardNo, byte[] CApdu, int nCAPDULen, byte[] RApdu, ref int nRAPDULen, double OutTime);

        //M1卡相关API------------------------------------------------------------------------------------------
        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int PiccMemActive(UInt16 usDelayTimes, byte[] pucCardType, byte[] pucUID);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int PiccMemAuthSector(byte ucSectorNo, byte ucKeyType, byte[] ucKey);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int PiccMemReadBlock(byte ucBlockNo, byte[] ucBlockData);

        [DllImport("HxgcDeviceApi_J10.dll", CallingConvention = CallingConvention.StdCall)]
        static extern int PiccMemWriteBlock(byte ucBlockNo, byte[] ucBlockData);

        #endregion

        public static IdCardInfo ReadId()
        {
            int iPort = 0;
            int iResult = 0;
            int iIfOpen = 1;//自动打开设备标志。如果设置为1，则在接口内部自动实现打开设备和关闭设备，无需调用者再添加。
            string strMsg = "";
            string strIDBase = "";

            StringBuilder strSAMID = new StringBuilder (64 + 1);
            string strManaID;

            byte[] byCHMsg = new byte[256 + 1];        //个人基本信息
            uint uiCHMsgSize = 0;                       //个人基本信息字节数
            byte[] byPHMsg = new byte[1024 + 1];       //照片信息
            uint uiPHMsgSize = 0;	                    //照片信息字节数
            byte[] byFPMsg = new byte[1024 + 1];       //指纹信息
            uint uiFPMsgSize = 0;                       //指纹信息字节数
            
            int iIsSaveToBmp = 0;

            byte[] byBgrBuffer = new byte[38556];    //解码后图片BGR编码值
            byte[] byRgbBuffer = new byte[38808];    //解码后图片RGB编码值
            byte[] byBmpBuffer = new byte[38862];    //解码后图片RGB编码值

            var idCard = new IdCardInfo();

            do
            {

                //iPort = GetPortNum();//获得端口号
                iPort = 1001;//直接使用USB口

                //获得设备SAM模块ID。
                //PS ：SAM模块ID为二代证设备唯一标志ID；
                //PS2：此函通常用来数来区分设备或判断设备是否连接正常；若只读卡信息的话无需添加此函数。
                iResult = SDT_GetSAMIDToStr(iPort, strSAMID, iIfOpen);
                if(0x90 != iResult)
                {
                    throw new Exception($"获取SAMID号失败，错误代码：{iResult:D}");
                }

                byte[] byManaID = new byte[8];

                //寻卡
                iResult = SDT_StartFindIDCard(iPort, byManaID, iIfOpen);
                if(0x9F != iResult)
                {
                    throw new Exception($"寻卡失败，错误代码：{iResult:D}");
                }

                //选卡
                byManaID.Initialize();
                iResult = SDT_SelectIDCard(iPort, byManaID, iIfOpen);
                if(0x90 != iResult)
                {
                    throw new Exception($"选卡失败，错误代码：{iResult:D}");
                }
                strManaID = GFunction.ByteArryToHexString(byManaID, 0, 8);//获得ManaID字符串

                //读取身份证个人基本信息、照片信息和指纹信息；
                //PS：指纹信息需要专门的指纹比对设备，这里只获取加密的原始数据。
                iResult = SDT_ReadBaseFPMsg(iPort, byCHMsg, ref uiCHMsgSize, byPHMsg, ref uiPHMsgSize, byFPMsg, ref uiFPMsgSize, iIfOpen);
                if (0x21 == iResult)//0501模块(一种老模块)无法读取指纹信息,会返回0x21错误，这里进行兼容处理；这种模块早就不用了，实际可以不做处理。
                {
                    iResult = SDT_ReadBaseMsg(iPort, byCHMsg, ref uiCHMsgSize, byPHMsg, ref uiPHMsgSize, iIfOpen);//采用只读卡信息和照片，不读指纹信息的接口
                }

                if (0x90 != iResult)
                {
                    throw new Exception($"读取身份信息失败，错误代码：{iResult:D}");
                }

                //解码照片数据，获得BGR格式数据
                //iIsSaveToBmp = 1;     //调用解码库unpack函数后，由接口自动生成名为zp.bmp的图片文件，该BMP文件可直接打开，不用B、R转换
                iIsSaveToBmp = 0;       //不自动生成zp.bmp图片

                //PS    ：解码库需要依赖授权文件（license.dat）；要确保“当前工作目录下”license.dat文件存在且正确，否则会返回-22和-12的错误
                //PS2   ：若设置iIsSaveToBmp = 1，即由接口自动生成zp.bmp文件，请确认“当前工作目录”具有写权限，否侧接口会崩溃（WIN7以上系统需注意此项）
                iResult = unpack(byPHMsg, byBgrBuffer, iIsSaveToBmp);
                if (1 != iResult)
                {
                    throw new Exception($"照片解码失败，错误代码：{iResult:D}");
                }

                //拼接BMP图片格式头，14字节
                byte[] byBmpHead = new byte[14] { 0x42, 0x4D, 0xCE, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00 };
                Array.Copy(byBmpHead, 0, byBmpBuffer, 0, 14);

                //拼接BMP图像信息，40字节
                byte[] byBmpInfo = new byte[40]{   0x28,0x00,0x00,0x00,//结构所占用40字节    
                    0x66,0x00,0x00,0x00,//位图的宽度102像素
                    0x7E,0x00,0x00,0x00,//位图的高度126像素
                    0x01,0x00,          //目标设备的级别必须为1
                    0x18,0x00,          //每个像素所需的位数24
                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00//......其他信息省略为0
                };
                Array.Copy(byBmpInfo, 0, byBmpBuffer, 14, 40);
                
                //将解码后的BGR格式数据进行B、R互换
                iResult = GFunction.bgr2rgb(byBgrBuffer, byBgrBuffer.Length, byRgbBuffer, byRgbBuffer.Length, 102, 126);
                if (iResult <= 0)
                {
                    throw new Exception("照片数据B、R互换失败.");
                }
                Array.Copy(byRgbBuffer, 0, byBmpBuffer, 54, iResult);

                //写入文件
                int iBmpSize = 54 + iResult;
                if (!GFunction.tool_WriteOneFile("zp1.bmp", byBmpBuffer, iBmpSize))
                {
                    throw new Exception("保存照片数据失败.");
                }

                //截取个人信息数据。信息采用UNICODE存储，具体格式参可见《二代证机读信息说明.doc》
                byte[] byName = new byte[30];
                byte[] bySex = new byte[2];
                byte[] byRace = new byte[4];
                byte[] byBirth = new byte[16];
                byte[] byAddress = new byte[70];
                byte[] byID = new byte[36];
                byte[] byCompany = new byte[30];
                byte[] byBeginDate = new byte[16];
                byte[] byEndDate = new byte[16];

                Array.Copy(byCHMsg, 0, byName, 0, 30);
                Array.Copy(byCHMsg, 30, bySex, 0, 2);
                Array.Copy(byCHMsg, 32, byRace, 0, 4);
                Array.Copy(byCHMsg, 36, byBirth, 0, 16);
                Array.Copy(byCHMsg, 52, byAddress, 0, 70);
                Array.Copy(byCHMsg, 122, byID, 0, 36);
                Array.Copy(byCHMsg, 158, byCompany, 0, 30);
                Array.Copy(byCHMsg, 188, byBeginDate, 0, 16);
                Array.Copy(byCHMsg, 204, byEndDate, 0, 16);

                using (var img = Image.FromFile("zp1.bmp"))
                {
                    idCard = new IdCardInfo
                    {
                        Name = Encoding.Unicode.GetString(byName).Trim(),
                        Gender = Encoding.Unicode.GetString(bySex).Trim(),
                        Nation = Encoding.Unicode.GetString(byRace).Trim(),
                        DateOfBirth = Encoding.Unicode.GetString(byBirth).Trim(),
                        ResidentialAddress = Encoding.Unicode.GetString(byAddress).Trim(),
                        CardNumber = Encoding.Unicode.GetString(byID).Trim(),
                        Authority = Encoding.Unicode.GetString(byCompany).Trim(),
                        StartDate = Encoding.Unicode.GetString(byBeginDate).Trim(),
                        ExpirationDate = Encoding.Unicode.GetString(byEndDate).Trim(),
                        PhotoBase64 = BytesToBase64(ImageToByteArray(img)),
                    };
                }

            } while (false);

            return idCard;
        }

        public static object WriteIc(string data)
        {
            int iResult = 0;
            string strMsg = "";
            string strData = "";

            int iSectorIdx = 0;     //扇区索引
            byte byBlockIdx = 0;    //块索引

            byte[] byBlockData;    //块数据（每个块最大16字节）
            string strBlockData;

            do
            {
                //M1卡写块
                iSectorIdx = 0;//获得扇区索引
                byBlockIdx = (byte)(iSectorIdx * 4 + 01);//计算块绝对索引 = 扇区索引 * 4 + 块相对索   

                strBlockData = data;
                byBlockData = Encoding.Unicode.GetBytes(strBlockData);//获得写入数据块字节数组（Unicode）
                //byBlockData = Encoding.ASCII.GetBytes(strBlockData);//获得写入数据块字节数组（ASCII）

                iResult = PiccMemWriteBlock(byBlockIdx, byBlockData);
                if (0 != iResult)
                {
                    throw new Exception($"M1卡写块失败，错误代码：0x{iResult:X}");
                }

                return new
                {
                    Message= "M1卡写块01成功"
                };

            } while (false);
        }

        public static object ReadIc()
        {
            try
            {
                OpenIc();

                int iResult = 0;
                string strMsg = "";
                string strData = "";

                int iSectorIdx = 0;  //扇区索引
                byte byBlockIdx = 0;   //块索引

                byte[] byBlockData = new byte[16];   //块数据（每个块最大16字节）
                string strBlockData;

                do
                {
                    //M1卡读块
                    iSectorIdx = 0;//获得扇区索引
                    byBlockIdx = (byte)(iSectorIdx * 4 + 01);//计算块绝对索引 = 扇区索引 * 4 + 块相对索引

                    iResult = PiccMemReadBlock(byBlockIdx, byBlockData);
                    if (0 != iResult)
                    {
                        throw new Exception($"M1卡读块失败，错误代码：0x{iResult:X}");
                    }

                    strBlockData = Encoding.Unicode.GetString(byBlockData);//Unicode字符集
                    //strBlockData = Encoding.ASCII.GetString(byBlockData);//ASCII字符集

                    return new
                    {
                        Message = $"M1卡读块{01:D2}成功",
                        数据 = strBlockData
                    };
                } while (false);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        private static object OpenIc()
        {
            int iPort = 0;
            int iResult = 0;
            int iBuad = 115200;//波特率只支持115200
            string strMsg = "";
            string strData = "";

            byte[] byCardType = new byte[1];   //卡片类型
            byte[] byUID = new byte[4];        //卡片ID

            string strCardType;
            string strUID;

            string strSectorPass;                   //密钥字符串
            byte byKeyType = 0;                     //密钥类型
            byte[] bySectorPass = new byte[6];     //密钥字节数组

            byte bySectorIdx = 0;  //扇区索引

            do
            {
                //iPort = GetPortNum();//获得端口号
                iPort = 1001;//直接使用USB口

                //打开设备
                iResult = CarderOpen(iPort, iBuad);
                if (0 != iResult)
                {
                    throw new Exception($"打开设备失败，错误代码：{iResult:D}");
                }

                //M1卡激活
                iResult = PiccMemActive(0, byCardType, byUID);
                if (0 != iResult)
                {
                    throw new Exception($"M1卡激活失败，错误代码：0x{iResult:X}");
                }
                strCardType = GFunction.ByteArryToHexString(byCardType, 0, byCardType.Length);
                strUID = GFunction.ByteArryToHexString(byUID, 0, byUID.Length);

                //byKeyType = 0x60;// KEYA
                byKeyType = 0x61;
                
                //M1卡认证
                strSectorPass = "FFFFFFFFFFFF".Replace(" ", "");//密钥字符串（去空格）
                GFunction.HexStringToByteArray(strSectorPass, bySectorPass, bySectorPass.Length);//获得密钥字节数组

                bySectorIdx = (byte)0;//获得扇区索引

                iResult = PiccMemAuthSector(bySectorIdx, byKeyType, bySectorPass);
                if (0 != iResult)
                {
                    throw new Exception($"卡认证失败，错误代码：0x{iResult:X}");
                }

                return new
                {
                    Message = $"M1卡激活认证成功.",
                    卡片类型 = $"{strCardType}",
                    卡片ID = $"{strUID}",
                    认证扇区 = $"{bySectorIdx:D2}"
                };

            } while (false);
        }

        private static int GetPortNum()
        {
            //int iPort = comboBox_dev_port.SelectedIndex;
            //if (0 == iPort)  //是否为USB口
            //{
            //    iPort = 1001;
            //}

            //return iPort;
            throw new NotImplementedException();
        }
        private static byte[] ImageToByteArray(System.Drawing.Image imageIn)
        {
            using (var ms = new MemoryStream())
            {
                imageIn.Save(ms,imageIn.RawFormat);
                return  ms.ToArray();
            }
        }

        private static string BytesToBase64(byte[] bytes)
        {
            string base64String = Convert.ToBase64String(bytes, 0, bytes.Length);
            return "data:image/png;base64," + base64String;
        }
    }
}